#!/usr/bin/python

import sys
import applib # You must install the applib directory in your Python module path.
from applib.logging import default_logger as log
from optparse import OptionParser
import shutil
import os

log.setName("apppackage")

# Parse options.
parser = OptionParser(usage="%prog [options] NAME\n       %prog [options] NAME OUTFILE\n       %prog [options] NAME VERSION OUTFILE")
applib.general.CommandLine.applyOptions(parser)
parser.add_option("-m", "--additional-space", help="Specify the additional space that should be placed in the Ext2 filesystem (in MB).  Default 1MB.", dest="app_additional_space", action="store", default=1)
parser.add_option("-v", "--verbose", help="Display additional information (such as directories still in use or files ignored because they aren't specifically owned by the application) during the link / unlink / relink process.", dest="verbose", action="store_true", default=False)
parser.add_option("", "--author", help="Specify the author of the package.", dest="app_author", default=None)
# TODO: Support multiple architectures on the command line.
parser.add_option("", "--arch", help="Specify the base architecture (default: i586).", dest="app_arch", default="i586")
(options, args) = parser.parse_args()

log.setVerbose(options.verbose)

appname = None
appver = None
outfile = None

if (len(args) == 1):
	appname = args[0]
	outfile = None # Automatically determined after fetching the app name / version.
elif (len(args) == 2):
	appname = args[0]
	outfile = args[1]
elif (len(args) == 3):
	appname = args[0]
	appver = args[1]
	outfile = args[2]
else:
	log.showErrorW("Invalid number arguments.  Type apppackage --help for usage information.")
	sys.exit(1)

# Get the application type from the command line arguments.
apptype = applib.general.CommandLine.getTypeFromOptions(options)

# Create an instance of InstalledApplication.
app = applib.fs.InstalledApplication(apptype, appname, appver)
try:
	app.autodetect()
except applib.general.NoVersionsException:
	log.showErrorW("Unable to detect the application version.  Specify it as a second parameter.")
	sys.exit(1)
except applib.general.MultipleVersionsException:
	log.showErrorW("More than one version of application detected.  Specify the version you")
	log.showErrorO("want to link as a second parameter.")
	sys.exit(1)
except applib.general.ApplicationNotFoundException:
	log.showErrorW("Unable to locate the application on disk (to scan for versions).")
	log.showErrorO("The location apppackage looked in was:")
	log.showErrorO("  * " + app.location_unversioned())
	sys.exit(1)

# Check to make sure the application exists.
if (not app.exists()):
	log.showErrorW("Unable to locate the application on disk.  The location apppackage looked in was:")
	log.showErrorO("  * " + app.location())
	sys.exit(1)

# See if the outfile name should be automatically generated.
if (outfile == None):
	outfile = "./" + app.name.lower() + "-" + app.version + ".afs"

# Check to make sure the file does not exist on disk.
package = applib.packaging.Package(outfile)
if (package.exists()):
	log.showErrorW("The output file already exists on disk:")
	log.showErrorO("  * " + outfile)
	sys.exit(1)

log.showInfoW("Packaging " + app.name + ", version " + app.version + "...", True)

# Calculate how big to create the package (and then create it).
s = app.size() + int(options.app_additional_space) * 1024 * 1024
try:
	package.create(s)
except applib.packaging.PackageCreationFailureException():
	log.showErrorW("Unable to create package at " + outfile + ".")
	sys.exit(1)
except applib.packaging.PackageAlreadyExistsException():
	log.showErrorW("The output file already exists on disk:")
	log.showErrorO("  * " + outfile)
	sys.exit(1)

# Mount the package.
try:
	mountpoint = package.mount()
except applib.packaging.PackageAlreadyMountedException:
	log.showErrorW("The package appears to be already mounted.  Unable to continue.")
	sys.exit(1)
except applib.packaging.PackageNotFoundException:
	log.showErrorW("The package file appears to have been deleted between the time")
	log.showErrorO("it was created, and the time the application attempted to mount")
	log.showErrorO("it.  Unable to continue.")
	sys.exit(1)
except applib.packaging.PackageMountFailureException:
	log.showErrorW("AppMount was unable to mount the package file.  See above for")
	log.showErrorO("error messages.")
	sys.exit(1)
log.showInfoW("Package mounted successfully.")

# Copy the files.
log.showInfoW("Copy the application tree to the mountpoint...")
total_files = 0
total_success = 0
for root, dirs, files in os.walk(app.location()):
	for name in dirs:
		total_files += 1
		dstdir = os.path.join(os.path.join(mountpoint, root[len(app.location())+1:]), name)
		try:
			os.mkdir(dstdir)
			total_success += 1
		except OSError:
			log.showErrorW("Unable to create directory: " + os.path.join(root[len(app.location()):], name))
		except IOError:
			log.showErrorW("Unable to create directory: " + os.path.join(root[len(app.location()):], name))
	for name in files:
		total_files += 1
		dstfile = os.path.join(os.path.join(mountpoint, root[len(app.location())+1:]), name)
		try:
			shutil.copy2(os.path.join(root, name), dstfile)
			total_success += 1
		except shutil.Error:
			log.showErrorW("Unable to copy file       : " + os.path.join(root[len(app.location()):], name))
		except IOError:
			log.showErrorW("Unable to copy file       : " + os.path.join(root[len(app.location()):], name))
if (total_files == total_success):
	log.showSuccessW("Successfully copied the entire application into the package.")
else:
	log.showWarningW("Successfully copied " + str(total_success) + " out of " + str(total_files) + " files.  Application")
	log.showWarningW("not operate correctly.")

# Write the /ApplicationInfo file (if it doesn't exist).
if (os.path.exists(os.path.join(mountpoint, "ApplicationInfo"))):
	# TODO: Allow an option on the command line to overwrite the /ApplicationInfo file anyway.
	log.showWarningW("Using the /ApplicationInfo file specified in the application directory.")
	log.showWarningO("Any information you may have specified via --author or --arch will be")
	log.showWarningO("ignored.")
else:
	pinfo = applib.packaging.PackageInfoWriter(app)
	pinfo.setAuthor(options.app_author)
	pinfo.setArchs([
		applib.packaging.PackageArchitecture(options.app_arch)
		])
	with open(os.path.join(mountpoint, "ApplicationInfo"), "w") as f:
		f.write(pinfo.getXML())
	log.showInfoW("Wrote the /ApplicationInfo file.")

# Unmount the package.
try:
	package.unmount()
except applib.packaging.PackageNotMountedException:
	log.showErrorW("The package appears to be already unmounted.  Unable to continue.")
	sys.exit(1)
except applib.packaging.PackageNotFoundException:
	log.showErrorW("The package file appears to have been deleted between the time")
	log.showErrorO("it was mounted, and the time the application attempted to unmount")
	log.showErrorO("it.  Unable to continue.")
	sys.exit(1)
except applib.packaging.PackageUnmountFailureException:
	log.showWarningW("FUSE was unable to unmount the package file.  See above for")
	log.showWarningO("error messages.  By unmounting manually, you should get the")
	log.showWarningO("desired result package.")
	sys.exit(1)
log.showInfoW("Package unmounted successfully.")
log.showSuccessW("The application " + app.name + " was successfully packaged into:")
log.showSuccessO("  * " + outfile)
sys.exit(0)
